﻿/*
 *Author: Jeff Liu 
 *
 *Under MIT License
 *jebberwocky@gmail.com
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using CtpGglMap.Core;
using CtpGglMap.Util;
using CtpGglMap.Exception;

namespace CtpGglMap.Impl
{
    public class StaticGMap: IMap
    {
        private List<IMapItem> items = new List<IMapItem>();

        public GeoPoint Center {get; set; }

        /// <summary>
        /// use Const.MapType
        /// </summary>
        public string MapType{get;set;}

        public GeoPoint NEBound{get;set; }

        public GeoPoint SWBound {get;set; }

        public int Zoom{get;set;}

        public bool Sensor { get; set; }

        private int _height;
        /// <summary>
        /// Height, max 640
        /// 640x640 is the largest image size allowed
        /// </summary>
        public int Height
        {
            get
            {
                return _height;
            }
            set
            {
                _height = (value > 640) ? 640 : value;
            }
        }

        private int _width;
        /// <summary>
        /// Width, max 640
        /// 640x640 is the largest image size allowed
        /// </summary>
        public int Width {
            get 
            { 
                return _width;
            }
            set
            {
                _width = (value>640)?640:value;
            }
        }

        private bool showMarkersPath  = false;

        public bool ShowMarkersPath
        {
            get
            {
                return this.showMarkersPath;
            }
            set
            {
                this.showMarkersPath = value;
            }
        }

        public String MarkersPathWeight { get; set; }

        public String MarkersPathColor { get; set; }

        /// <summary>
        /// use Const.ImageFormats
        /// </summary>
        public String ImageFormat { get; set; }

        /// <summary>
        ///  when AutoSize, zoom = (calculated zoom) + ZoomDelta
        /// </summary>
        public int ZoomDelta { get; set; }

        /// <summary>
        /// Add IMapItem to map
        /// </summary>
        /// <param name="item"></param>
        public void AddItem(IMapItem item)
        {
            items.Add(item);
        }

        /// <summary>
        /// get all map items
        /// </summary>
        /// <returns></returns>
        public List<IMapItem> GetItems()
        {
            return items;
        }

        private ICluster _cluster;

        /// <summary>
        /// Default: No Cluster
        /// </summary>
        public ICluster Cluster
        {
            set { _cluster = value; _cluster.Map = this; }
            get { return _cluster; }
        }

        private bool _autoFit = false;

        /// <summary>
        ///  whether the map zoom & center will be automatically set with GMarks' boundry
        /// </summary>
        public bool AutoFit
        {
            set { this._autoFit = value; }
            get
            {
                return this._autoFit;
            }
        }

        /// <summary>
        /// refer to :http://code.google.com/apis/maps/documentation/staticmaps/
        /// Static Map URLs are restricted to 2048 characters in size. In practice, you will probably not have need for URLs longer than this, 
        /// unless you produce complicated maps with a high number of markers and paths. Note, however, that certain characters may be 
        /// URL-encoded by browsers and/or services before sending them off to the Static Map service, resulting in increased character usage.
        /// 
        /// Exception will be throw when generated url exceedes 2048 characters
        /// </summary>
        /// <returns></returns>
        public Stream Fetch()
        {
            String url = UrlGenerate();
            if (!string.IsNullOrEmpty(url))
            {
                if (url.Length > CtpGglMap.Core.StaticMap.Const.MAX_REQUEST_CHARACTER_LENGTH)
                    throw new GoogleRequestException(null, "Static Map URLs are restricted to 2048 characters in size. Please remove some markers or paths. length:" + url.Length, url);
                System.Net.HttpWebRequest _HttpWebRequest = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(url);
                _HttpWebRequest.AllowWriteStreamBuffering = true;
                System.Net.WebResponse _WebResponse = null;
                try
                {
                    _WebResponse = _HttpWebRequest.GetResponse();
                }
                catch (System.Net.WebException webExcep)
                {
                    throw new GoogleRequestException(webExcep, "Static Map URLs are restricted to 2048 characters in size. Please remove some markers or paths",
                        url);
                }
                if (_WebResponse != null)
                {
                    System.IO.Stream _WebStream = _WebResponse.GetResponseStream();
                    return _WebStream;
                }
                else
                    return null;
            }
            else return null;
        }

        public String GoogleRequestUrl
        {
            get { return UrlGenerate(); }
        }

        private String UrlGenerate()
        {
            if (this.Cluster != null)
            {
                return this.Cluster.GetShortenUrl();
            }
            else
            {
                //if no cluster is assigned, use default method to generate url
                StringBuilder sb = new StringBuilder(CtpGglMap.Core.StaticMap.Const.GOOGLE_STATIC_MAPS_API_URL + "?");
                //if "implicit positioning," calculate the center & zoom level from map items
                if (AutoFit)
                {
                    Center = GeoUtil.CalculateCenterFromMapItem(this.GetItems());
                    Zoom = GeoUtil.ZoomToFit(this, this.GetItems(), this.ZoomDelta);
                }
                //build up parameters
                List<String> param = new List<string>();
                if (Center != null)
                {
                    param.Add(string.Format("center={0},{1}", Center.Lat, Center.Lng));
                }
                else
                {
                    return null;
                }
                param.Add("zoom=" + this.Zoom);
                param.Add(String.Format("size={0}x{1}", this.Width, this.Height));
                if (!String.IsNullOrEmpty(this.MapType))
                    param.Add("maptype=" + this.MapType);
                param.Add("sensor=" + this.Sensor.ToString().ToLower());
                //show Markers Path or not
                List<GeoPoint> markersPoints = new List<GeoPoint>();

                foreach (IMapItem item in items)
                {
                    if (item != null)
                    {
                        if (item.GetType() == typeof(GMarker) || item.GetType().IsSubclassOf(typeof(GMarker)))
                        {
                            GMarker marker = item as GMarker;
                            markersPoints.Add(marker.Point);
                            param.Add("markers=" + marker.GetUrl());
                        }
                        else if (item.GetType() == typeof(GPath))
                        {
                            GPath path = item as GPath;
                            param.Add("path=" + path.GetUrl());
                        }
                    }
                }
                if (this.ShowMarkersPath)
                {
                    GPath path = new GPath(markersPoints);
                    path.Weight = this.MarkersPathWeight;
                    path.Color = this.MarkersPathColor; 
                    param.Add("path=" + path.GetUrl());
                }
                //Stringify
                for (int i = 0; i < param.Count; i++)
                {
                    sb.Append(param[i]);
                    if (i < param.Count - 1)
                        sb.Append("&");
                }
                return sb.ToString();
            }
        }
    }
}
